package pl.agh.convexhulltutor.algorithms;

import java.util.ArrayList;
import java.util.List;

import pl.agh.convexhulltutor.gui.DrawPanel;
import pl.agh.convexhulltutor.gui.MessageBox;
import pl.agh.convexhulltutor.structures.Line;
import pl.agh.convexhulltutor.structures.Point;

public abstract class Algorithm {
	private static final double EPSILON = 0.00001;

	private DrawPanel drawPanel;

	protected List<List<Point>> pointsPerStep = new ArrayList<List<Point>>();
	protected List<List<Line>> linesPerStep = new ArrayList<List<Line>>();

	protected int currentStep = 0;
	protected int lastCalculatedStep = 0;
	protected int maxStep = -1;

	public Algorithm(DrawPanel drawPanel) {
		this.drawPanel = drawPanel;
	}

	private boolean outputChanged(List<Point> newPoints, List<Line> newLines) {
		List<Point> oldPoints = pointsPerStep.get(pointsPerStep.size() - 1);
		List<Line> oldLines = linesPerStep.get(linesPerStep.size() - 1);
		if (oldPoints.size() != newPoints.size()
				|| oldLines.size() != newLines.size()) {
			return true;
		}
		for (int i = 0; i < oldPoints.size(); i++) {
			if (!oldPoints.get(i).isIdentical(newPoints.get(i))) {
				return true;
			}
		}
		for (int i = 0; i < oldLines.size(); i++) {
			if (!oldLines.get(i).isIdentical(newLines.get(i))) {
				return true;
			}
		}
		return false;
	}

	protected List<Point> getPointsDeepCopy(List<Point> points) {
		List<Point> copiedPoints = new ArrayList<Point>();

		for (Point point : points) {
			copiedPoints.add(new Point(point));
		}
		return copiedPoints;
	}

	protected List<Line> getLinesDeepCopy(List<Line> lines) {
		List<Line> copiedLines = new ArrayList<Line>();

		for (Line line : lines) {
			copiedLines.add(new Line(line));
		}
		return copiedLines;
	}

	protected void repaint() {
		drawPanel.setPoints(pointsPerStep.get(currentStep));
		drawPanel.setLines(linesPerStep.get(currentStep));
		drawPanel.repaint();
	}

	protected Point getUpdatedPoint(Point currentPoint, List<Point> points) {
		for (Point point : points) {
			if (currentPoint.compareCoordinates(point, true) == 0) {
				return point;
			}
		}
		return null;
	}

	public abstract void execute(List<Point> points, List<Line> lines);

	public void previousStep() {
		if (currentStep > 0) {
			currentStep--;
			repaint();
		} else {
			MessageBox.warn("You are at the beginning of algorithm execution");
		}
	}

	public void nextStep() {
		if (maxStep == -1 || maxStep > currentStep) {
			currentStep++;
			if (lastCalculatedStep == 0) {
				pointsPerStep.add(getPointsDeepCopy(drawPanel.getPoints()));
				linesPerStep.add(new ArrayList<Line>());
			}
			if (lastCalculatedStep >= currentStep) {
				repaint();
			}
			if (currentStep > lastCalculatedStep) {
				List<Point> points = getPointsDeepCopy(pointsPerStep
						.get(currentStep - 1));
				List<Line> lines = getLinesDeepCopy(linesPerStep
						.get(currentStep - 1));
				do {
					execute(points, lines);
				} while ((!outputChanged(points, lines)));
				pointsPerStep.add(points);
				linesPerStep.add(lines);
				lastCalculatedStep++;
				repaint();
			}
		} else {
			MessageBox.warn("This was the last step of algorithm execution");
		}
	}

	private static double calculateDeterminant(Point segmentStart,
			Point segmentEnd, Point leftPositivePoint) {
		double a = segmentStart.getX() - leftPositivePoint.getX();
		double b = segmentEnd.getY() - leftPositivePoint.getY();
		double c = segmentEnd.getX() - leftPositivePoint.getX();
		double d = segmentStart.getY() - leftPositivePoint.getY();
		return a * b - c * d;
	}

	public static int getDeterminantSign(Point segmentStart, Point segmentEnd,
			Point leftPositivePoint) {
		double result = calculateDeterminant(segmentStart, segmentEnd,
				leftPositivePoint);
		if (Math.abs(result) < EPSILON) {
			return 0;
		} else if (result > 0) {
			return 1;
		} else {
			return -1;
		}
	}

	public static boolean isPointInsideTriangle(Point pointOfInterest,
			Point triangleVertex1, Point triangleVertex2, Point triangleVertex3) {
		int determinant1Sign = getDeterminantSign(triangleVertex1,
				triangleVertex2, pointOfInterest);
		int determinant2Sign = getDeterminantSign(triangleVertex2,
				triangleVertex3, pointOfInterest);
		int determinant3Sign = getDeterminantSign(triangleVertex3,
				triangleVertex1, pointOfInterest);

		if (pointOfInterest.isIdentical(triangleVertex1)
				|| pointOfInterest.isIdentical(triangleVertex2)
				|| pointOfInterest.isIdentical(triangleVertex3)) {
			return false;
		}
		if (determinant1Sign == 0 || determinant2Sign == 0
				|| determinant3Sign == 0) {
			return true;
		}
		if (determinant1Sign > 0 && determinant2Sign > 0
				&& determinant3Sign > 0) {
			return true;
		}
		if (determinant1Sign < 0 && determinant2Sign < 0
				&& determinant3Sign < 0) {
			return true;
		}
		return false;
	}
}
